/***************************************************************************/
/*                                                                         */
/*  pfrgload.c                                                             */
/*                                                                         */
/*    FreeType PFR glyph loader (body).                                    */
/*                                                                         */
/*  Copyright 2002, 2003, 2005, 2007 by                                    */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/


#include "pfrgload.h"
#include "pfrsbit.h"
#include "pfrload.h"            /* for macro definitions */
#include FT_INTERNAL_DEBUG_H

#include "pfrerror.h"

#undef  FT_COMPONENT
#define FT_COMPONENT trace_pfr


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                      PFR GLYPH BUILDER                        *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


FT_LOCAL_DEF(void)
pfr_glyph_init(PFR_Glyph glyph,
               FT_GlyphLoader loader)
{
    FT_ZERO(glyph);

    glyph->loader = loader;
    glyph->path_begun = 0;

    FT_GlyphLoader_Rewind(loader);
}


FT_LOCAL_DEF(void)
pfr_glyph_done(PFR_Glyph glyph)
{
    FT_Memory memory = glyph->loader->memory;


    FT_FREE(glyph->x_control);
    glyph->y_control = NULL;

    glyph->max_xy_control = 0;
    #if 0
    glyph->num_x_control = 0;
    glyph->num_y_control = 0;
    #endif

    FT_FREE(glyph->subs);

    glyph->max_subs = 0;
    glyph->num_subs = 0;

    glyph->loader = NULL;
    glyph->path_begun = 0;
}


/* close current contour, if any */
static void
pfr_glyph_close_contour(PFR_Glyph glyph)
{
    FT_GlyphLoader loader = glyph->loader;
    FT_Outline* outline = &loader->current.outline;
    FT_Int last, first;


    if (!glyph->path_begun)
        return;

    /* compute first and last point indices in current glyph outline */
    last = outline->n_points - 1;
    first = 0;
    if (outline->n_contours > 0)
        first = outline->contours[outline->n_contours - 1];

    /* if the last point falls on the same location than the first one */
    /* we need to delete it                                            */
    if (last > first)
    {
        FT_Vector* p1 = outline->points + first;
        FT_Vector* p2 = outline->points + last;


        if (p1->x == p2->x && p1->y == p2->y)
        {
            outline->n_points--;
            last--;
        }
    }

    /* don't add empty contours */
    if (last >= first)
        outline->contours[outline->n_contours++] = (short)last;

    glyph->path_begun = 0;
}


/* reset glyph to start the loading of a new glyph */
static void
pfr_glyph_start(PFR_Glyph glyph)
{
    glyph->path_begun = 0;
}


static FT_Error
pfr_glyph_line_to(PFR_Glyph glyph,
                  FT_Vector* to)
{
    FT_GlyphLoader loader = glyph->loader;
    FT_Outline* outline = &loader->current.outline;
    FT_Error error;


    /* check that we have begun a new path */
    if (!glyph->path_begun)
    {
        error = PFR_Err_Invalid_Table;
        FT_ERROR(("pfr_glyph_line_to: invalid glyph data\n"));
        goto Exit;
    }

    error = FT_GLYPHLOADER_CHECK_POINTS(loader, 1, 0);
    if (!error)
    {
        FT_UInt n = outline->n_points;


        outline->points[n] = *to;
        outline->tags[n] = FT_CURVE_TAG_ON;

        outline->n_points++;
    }

Exit:
    return error;
}


static FT_Error
pfr_glyph_curve_to(PFR_Glyph glyph,
                   FT_Vector* control1,
                   FT_Vector* control2,
                   FT_Vector* to)
{
    FT_GlyphLoader loader = glyph->loader;
    FT_Outline* outline = &loader->current.outline;
    FT_Error error;


    /* check that we have begun a new path */
    if (!glyph->path_begun)
    {
        error = PFR_Err_Invalid_Table;
        FT_ERROR(("pfr_glyph_line_to: invalid glyph data\n"));
        goto Exit;
    }

    error = FT_GLYPHLOADER_CHECK_POINTS(loader, 3, 0);
    if (!error)
    {
        FT_Vector* vec = outline->points + outline->n_points;
        FT_Byte* tag = (FT_Byte*)outline->tags + outline->n_points;


        vec[0] = *control1;
        vec[1] = *control2;
        vec[2] = *to;
        tag[0] = FT_CURVE_TAG_CUBIC;
        tag[1] = FT_CURVE_TAG_CUBIC;
        tag[2] = FT_CURVE_TAG_ON;

        outline->n_points = (FT_Short)(outline->n_points + 3);
    }

Exit:
    return error;
}


static FT_Error
pfr_glyph_move_to(PFR_Glyph glyph,
                  FT_Vector* to)
{
    FT_GlyphLoader loader = glyph->loader;
    FT_Error error;


    /* close current contour if any */
    pfr_glyph_close_contour(glyph);

    /* indicate that a new contour has started */
    glyph->path_begun = 1;

    /* check that there is space for a new contour and a new point */
    error = FT_GLYPHLOADER_CHECK_POINTS(loader, 1, 1);
    if (!error)
        /* add new start point */
        error = pfr_glyph_line_to(glyph, to);

    return error;
}


static void
pfr_glyph_end(PFR_Glyph glyph)
{
    /* close current contour if any */
    pfr_glyph_close_contour(glyph);

    /* merge the current glyph into the stack */
    FT_GlyphLoader_Add(glyph->loader);
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                      PFR GLYPH LOADER                         *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


/* load a simple glyph */
static FT_Error
pfr_glyph_load_simple(PFR_Glyph glyph,
                      FT_Byte* p,
                      FT_Byte* limit)
{
    FT_Error error = 0;
    FT_Memory memory = glyph->loader->memory;
    FT_UInt flags, x_count, y_count, i, count, mask;
    FT_Int x;


    PFR_CHECK(1);
    flags = PFR_NEXT_BYTE(p);

    /* test for composite glyphs */
    if (flags & PFR_GLYPH_IS_COMPOUND)
        goto Failure;

    x_count = 0;
    y_count = 0;

    if (flags & PFR_GLYPH_1BYTE_XYCOUNT)
    {
        PFR_CHECK(1);
        count = PFR_NEXT_BYTE(p);
        x_count = (count & 15);
        y_count = (count >> 4);
    }
    else
    {
        if (flags & PFR_GLYPH_XCOUNT)
        {
            PFR_CHECK(1);
            x_count = PFR_NEXT_BYTE(p);
        }

        if (flags & PFR_GLYPH_YCOUNT)
        {
            PFR_CHECK(1);
            y_count = PFR_NEXT_BYTE(p);
        }
    }

    count = x_count + y_count;

    /* re-allocate array when necessary */
    if (count > glyph->max_xy_control)
    {
        FT_UInt new_max = FT_PAD_CEIL(count, 8);


        if (FT_RENEW_ARRAY(glyph->x_control,
                           glyph->max_xy_control,
                           new_max))
            goto Exit;

        glyph->max_xy_control = new_max;
    }

    glyph->y_control = glyph->x_control + x_count;

    mask = 0;
    x = 0;

    for (i = 0; i < count; i++)
    {
        if ((i & 7) == 0)
        {
            PFR_CHECK(1);
            mask = PFR_NEXT_BYTE(p);
        }

        if (mask & 1)
        {
            PFR_CHECK(2);
            x = PFR_NEXT_SHORT(p);
        }
        else
        {
            PFR_CHECK(1);
            x += PFR_NEXT_BYTE(p);
        }

        glyph->x_control[i] = x;

        mask >>= 1;
    }

    /* XXX: for now we ignore the secondary stroke and edge definitions */
    /*      since we don't want to support native PFR hinting           */
    /*                                                                  */
    if (flags & PFR_GLYPH_EXTRA_ITEMS)
    {
        error = pfr_extra_items_skip(&p, limit);
        if (error)
            goto Exit;
    }

    pfr_glyph_start(glyph);

    /* now load a simple glyph */
    {
        FT_Vector pos[4];
        FT_Vector* cur;


        pos[0].x = pos[0].y = 0;
        pos[3] = pos[0];

        for (;;)
        {
            FT_UInt format, format_low, args_format = 0, args_count, n;


            /***************************************************************/
            /*  read instruction                                           */
            /*                                                             */
            PFR_CHECK(1);
            format = PFR_NEXT_BYTE(p);
            format_low = format & 15;

            switch (format >> 4)
            {
                case 0:                     /* end glyph */
                    FT_TRACE6(("- end glyph"));
                    args_count = 0;
                    break;

                case 1:                     /* general line operation */
                    FT_TRACE6(("- general line"));
                    goto Line1;

                case 4:                     /* move to inside contour  */
                    FT_TRACE6(("- move to inside"));
                    goto Line1;

                case 5:                     /* move to outside contour */
                    FT_TRACE6(("- move to outside"));
Line1:
                    args_format = format_low;
                    args_count = 1;
                    break;

                case 2:                     /* horizontal line to */
                    FT_TRACE6(("- horizontal line to cx.%d", format_low));
                    if (format_low > x_count)
                        goto Failure;
                    pos[0].x = glyph->x_control[format_low];
                    pos[0].y = pos[3].y;
                    pos[3] = pos[0];
                    args_count = 0;
                    break;

                case 3:                     /* vertical line to */
                    FT_TRACE6(("- vertical line to cy.%d", format_low));
                    if (format_low > y_count)
                        goto Failure;
                    pos[0].x = pos[3].x;
                    pos[0].y = glyph->y_control[format_low];
                    pos[3] = pos[0];
                    args_count = 0;
                    break;

                case 6:                     /* horizontal to vertical curve */
                    FT_TRACE6(("- hv curve "));
                    args_format = 0xB8E;
                    args_count = 3;
                    break;

                case 7:                     /* vertical to horizontal curve */
                    FT_TRACE6(("- vh curve"));
                    args_format = 0xE2B;
                    args_count = 3;
                    break;

                default:                    /* general curve to */
                    FT_TRACE6(("- general curve"));
                    args_count = 4;
                    args_format = format_low;
            }

            /***********************************************************/
            /*  now read arguments                                     */
            /*                                                         */
            cur = pos;
            for (n = 0; n < args_count; n++)
            {
                FT_UInt idx;
                FT_Int delta;


                /* read the X argument */
                switch (args_format & 3)
                {
                    case 0:                 /* 8-bit index */
                        PFR_CHECK(1);
                        idx = PFR_NEXT_BYTE(p);
                        if (idx > x_count)
                            goto Failure;
                        cur->x = glyph->x_control[idx];
                        FT_TRACE7((" cx#%d", idx));
                        break;

                    case 1:                 /* 16-bit value */
                        PFR_CHECK(2);
                        cur->x = PFR_NEXT_SHORT(p);
                        FT_TRACE7((" x.%d", cur->x));
                        break;

                    case 2:                 /* 8-bit delta */
                        PFR_CHECK(1);
                        delta = PFR_NEXT_INT8(p);
                        cur->x = pos[3].x + delta;
                        FT_TRACE7((" dx.%d", delta));
                        break;

                    default:
                        FT_TRACE7((" |"));
                        cur->x = pos[3].x;
                }

                /* read the Y argument */
                switch ((args_format >> 2) & 3)
                {
                    case 0:                 /* 8-bit index */
                        PFR_CHECK(1);
                        idx = PFR_NEXT_BYTE(p);
                        if (idx > y_count)
                            goto Failure;
                        cur->y = glyph->y_control[idx];
                        FT_TRACE7((" cy#%d", idx));
                        break;

                    case 1:                 /* 16-bit absolute value */
                        PFR_CHECK(2);
                        cur->y = PFR_NEXT_SHORT(p);
                        FT_TRACE7((" y.%d", cur->y));
                        break;

                    case 2:                 /* 8-bit delta */
                        PFR_CHECK(1);
                        delta = PFR_NEXT_INT8(p);
                        cur->y = pos[3].y + delta;
                        FT_TRACE7((" dy.%d", delta));
                        break;

                    default:
                        FT_TRACE7((" -"));
                        cur->y = pos[3].y;
                }

                /* read the additional format flag for the general curve */
                if (n == 0 && args_count == 4)
                {
                    PFR_CHECK(1);
                    args_format = PFR_NEXT_BYTE(p);
                    args_count--;
                }
                else
                    args_format >>= 4;

                /* save the previous point */
                pos[3] = cur[0];
                cur++;
            }

            FT_TRACE7(("\n"));

            /***********************************************************/
            /*  finally, execute instruction                           */
            /*                                                         */
            switch (format >> 4)
            {
                case 0:                     /* end glyph => EXIT */
                    pfr_glyph_end(glyph);
                    goto Exit;

                case 1:                     /* line operations */
                case 2:
                case 3:
                    error = pfr_glyph_line_to(glyph, pos);
                    goto Test_Error;

                case 4:                     /* move to inside contour  */
                case 5:                     /* move to outside contour */
                    error = pfr_glyph_move_to(glyph, pos);
                    goto Test_Error;

                default:                    /* curve operations */
                    error = pfr_glyph_curve_to(glyph, pos, pos + 1, pos + 2);

Test_Error:          /* test error condition */
                    if (error)
                        goto Exit;
            }
        } /* for (;;) */
    }

Exit:
    return error;

Failure:
Too_Short:
    error = PFR_Err_Invalid_Table;
    FT_ERROR(("pfr_glyph_load_simple: invalid glyph data\n"));
    goto Exit;
}


/* load a composite/compound glyph */
static FT_Error
pfr_glyph_load_compound(PFR_Glyph glyph,
                        FT_Byte* p,
                        FT_Byte* limit)
{
    FT_Error error = 0;
    FT_GlyphLoader loader = glyph->loader;
    FT_Memory memory = loader->memory;
    PFR_SubGlyph subglyph;
    FT_UInt flags, i, count, org_count;
    FT_Int x_pos, y_pos;


    PFR_CHECK(1);
    flags = PFR_NEXT_BYTE(p);

    /* test for composite glyphs */
    if (!(flags & PFR_GLYPH_IS_COMPOUND))
        goto Failure;

    count = flags & 0x3F;

    /* ignore extra items when present */
    /*                                 */
    if (flags & PFR_GLYPH_EXTRA_ITEMS)
    {
        error = pfr_extra_items_skip(&p, limit);
        if (error) goto Exit;
    }

    /* we can't rely on the FT_GlyphLoader to load sub-glyphs, because   */
    /* the PFR format is dumb, using direct file offsets to point to the */
    /* sub-glyphs (instead of glyph indices).  Sigh.                     */
    /*                                                                   */
    /* For now, we load the list of sub-glyphs into a different array    */
    /* but this will prevent us from using the auto-hinter at its best   */
    /* quality.                                                          */
    /*                                                                   */
    org_count = glyph->num_subs;

    if (org_count + count > glyph->max_subs)
    {
        FT_UInt new_max = (org_count + count + 3) & (FT_UInt) - 4;


        if (FT_RENEW_ARRAY(glyph->subs, glyph->max_subs, new_max))
            goto Exit;

        glyph->max_subs = new_max;
    }

    subglyph = glyph->subs + org_count;

    for (i = 0; i < count; i++, subglyph++)
    {
        FT_UInt format;


        x_pos = 0;
        y_pos = 0;

        PFR_CHECK(1);
        format = PFR_NEXT_BYTE(p);

        /* read scale when available */
        subglyph->x_scale = 0x10000L;
        if (format & PFR_SUBGLYPH_XSCALE)
        {
            PFR_CHECK(2);
            subglyph->x_scale = PFR_NEXT_SHORT(p) << 4;
        }

        subglyph->y_scale = 0x10000L;
        if (format & PFR_SUBGLYPH_YSCALE)
        {
            PFR_CHECK(2);
            subglyph->y_scale = PFR_NEXT_SHORT(p) << 4;
        }

        /* read offset */
        switch (format & 3)
        {
            case 1:
                PFR_CHECK(2);
                x_pos = PFR_NEXT_SHORT(p);
                break;

            case 2:
                PFR_CHECK(1);
                x_pos += PFR_NEXT_INT8(p);
                break;

            default:
                ;
        }

        switch ((format >> 2) & 3)
        {
            case 1:
                PFR_CHECK(2);
                y_pos = PFR_NEXT_SHORT(p);
                break;

            case 2:
                PFR_CHECK(1);
                y_pos += PFR_NEXT_INT8(p);
                break;

            default:
                ;
        }

        subglyph->x_delta = x_pos;
        subglyph->y_delta = y_pos;

        /* read glyph position and size now */
        if (format & PFR_SUBGLYPH_2BYTE_SIZE)
        {
            PFR_CHECK(2);
            subglyph->gps_size = PFR_NEXT_USHORT(p);
        }
        else
        {
            PFR_CHECK(1);
            subglyph->gps_size = PFR_NEXT_BYTE(p);
        }

        if (format & PFR_SUBGLYPH_3BYTE_OFFSET)
        {
            PFR_CHECK(3);
            subglyph->gps_offset = PFR_NEXT_LONG(p);
        }
        else
        {
            PFR_CHECK(2);
            subglyph->gps_offset = PFR_NEXT_USHORT(p);
        }

        glyph->num_subs++;
    }

Exit:
    return error;

Failure:
Too_Short:
    error = PFR_Err_Invalid_Table;
    FT_ERROR(("pfr_glyph_load_compound: invalid glyph data\n"));
    goto Exit;
}


static FT_Error
pfr_glyph_load_rec(PFR_Glyph glyph,
                   FT_Stream stream,
                   FT_ULong gps_offset,
                   FT_ULong offset,
                   FT_ULong size)
{
    FT_Error error;
    FT_Byte* p;
    FT_Byte* limit;


    if (FT_STREAM_SEEK(gps_offset + offset) ||
        FT_FRAME_ENTER(size))
        goto Exit;

    p = (FT_Byte*)stream->cursor;
    limit = p + size;

    if (size > 0 && *p & PFR_GLYPH_IS_COMPOUND)
    {
        FT_Int n, old_count, count;
        FT_GlyphLoader loader = glyph->loader;
        FT_Outline* base = &loader->base.outline;


        old_count = glyph->num_subs;

        /* this is a compound glyph - load it */
        error = pfr_glyph_load_compound(glyph, p, limit);

        FT_FRAME_EXIT();

        if (error)
            goto Exit;

        count = glyph->num_subs - old_count;

        /* now, load each individual glyph */
        for (n = 0; n < count; n++)
        {
            FT_Int i, old_points, num_points;
            PFR_SubGlyph subglyph;


            subglyph = glyph->subs + old_count + n;
            old_points = base->n_points;

            error = pfr_glyph_load_rec(glyph, stream, gps_offset,
                                       subglyph->gps_offset,
                                       subglyph->gps_size);
            if (error)
                goto Exit;

            /* note that `glyph->subs' might have been re-allocated */
            subglyph = glyph->subs + old_count + n;
            num_points = base->n_points - old_points;

            /* translate and eventually scale the new glyph points */
            if (subglyph->x_scale != 0x10000L || subglyph->y_scale != 0x10000L)
            {
                FT_Vector* vec = base->points + old_points;


                for (i = 0; i < num_points; i++, vec++)
                {
                    vec->x = FT_MulFix(vec->x, subglyph->x_scale) +
                             subglyph->x_delta;
                    vec->y = FT_MulFix(vec->y, subglyph->y_scale) +
                             subglyph->y_delta;
                }
            }
            else
            {
                FT_Vector* vec = loader->base.outline.points + old_points;


                for (i = 0; i < num_points; i++, vec++)
                {
                    vec->x += subglyph->x_delta;
                    vec->y += subglyph->y_delta;
                }
            }

            /* proceed to next sub-glyph */
        }
    }
    else
    {
        /* load a simple glyph */
        error = pfr_glyph_load_simple(glyph, p, limit);

        FT_FRAME_EXIT();
    }

Exit:
    return error;
}





FT_LOCAL_DEF(FT_Error)
pfr_glyph_load(PFR_Glyph glyph,
               FT_Stream stream,
               FT_ULong gps_offset,
               FT_ULong offset,
               FT_ULong size)
{
    /* initialize glyph loader */
    FT_GlyphLoader_Rewind(glyph->loader);

    glyph->num_subs = 0;

    /* load the glyph, recursively when needed */
    return pfr_glyph_load_rec(glyph, stream, gps_offset, offset, size);
}


/* END */